Web Standards

D3 is built on top of several common web standards. Don’t worry if you don’t know all the nitty-gritty details of these standards, you can pick this stuff up pretty quickly.

HTML

HTML (HyperText Markup Language) is a text format that most web pages are written in. HTML uses a standard set of tags to define the different structural components of a webpage: <h1>, <h2> tags define headers, <p> tags define paragraphs, <ol> and <ul> are orderered and unordered lists. Browsers have common ways to display these tags, so lists show up like lists, and headers like headers.

The <div> and <span> tags are special because browers don’t apply default styles to them, so HTML authors can use them to define custom groups.

The basic outline of an HTML page is something like this:

<!DOCTYPE html>
<html>
  <head>
    <title>TITLE GOES HERE</title>
  </head>
  <body>
    MAIN CONTENT GOES HERE
  </body>
</html>

    

CSS

CSS (Cascading Stylesheets) is a language for styling HTML pages.

CSS styles (also know as selectors) are typically applied to HTML tags based on their name, class, or ID.

Here are some simple CSS rules and how they apply.

<div>
  <p>Normal paragraph</p>

  <p class="red">Red paragraph</p>
</div>

<ol>
  <li id="some-id">Unique element</li>
  <li>Another list element</li>
  <li>
    <p>Paragraph inside list element</p>
    <p>Second paragraph</p>
  </li>
</ol>

    
/* Applied to all <p> tags */
p {
  color: blue;
}

/* Applied to all tags with the class "red" */
.red {
  background: red;
}

/* Applied to the tag with the id "some-id" */
#some-id {
  font-style: italic;
}

/* Applied only to <p> tags that are inside <li> tags */
li p {
  color: #0C0;
}
    

The DOM

When a browser displays an HTML page, it creates an interactive object graph from the tag hierarchy. This object graph is called the Document Object Model, or DOM.

The standard DOM API is somewhat verbose, so many libraries like jQuery and D3 provide some syntactic sugar that borrows from CSS notation.

Here are some examples of accessing the DOM programatically.

<div>
  <p>Normal paragraph</p>

  <p class="red">Red paragraph</p>
</div>

<ol>
  <li id="some-id">Unique element</li>
  <li>Another list element</li>
  <li>
    <p>Paragraph inside list element</p>
    <p>Second paragraph</p>
  </li>
</ol>

    
// DOM API
document.getElementById('some-id');
// <li id="some-id">Unique element</li>
document.getElementsByTagName('p').length;
// 4
var reds = document.getElementsByClassName('red');
// [<p class="red">Red paragraph</p>]
reds[0].innerText
// "Red paragraph"
    
// D3 Selection API
d3.select('p').size(); // select() only finds one
// 1
d3.selectAll('p').size(); // selectAll() finds all
// 4
var reds = d3.selectAll('.red');
// [ > Array[1] ]
reds.text();
// "Red paragraph"
    

The DOM also handles tracking elements as they are rendered, as well as events like mouse movement. You can attach listeners to these events to add various levels of interactivity to your page.

Here are some examples of adding listeners to the click, mouseover and mouseleave events. D3 has some nice helper methods for working with events as well.

<h1 id="click-me">
  Click on me!
</h1>

<p class="hover-me">
  Hover over me!
</p>

<p class="hover-me">
  OK now hover over here!
</p>

<p class="hover-me">
  Hover here too!
</p>
    
// DOM API
var clickMe = document.getElementById('click-me');
clickMe.onclick = function() {
  if (this.style.backgroundColor) {
    this.style.backgroundColor = '';
  } else {
    this.style.backgroundColor = 'red';
  }
}

// D3 Selection API. Note: it attaches the
// callbacks to each element in the selection
d3.selectAll('.hover-me')
  .on('mouseover', function() {
    this.style.backgroundColor = 'yellow';
  })
  .on('mouseleave', function() {
    this.style.backgroundColor = '';
  });
    
Note: In the D3 examples, the methods on the selection can chain (that is, they return themselves, so we can group them visually).

SVG

SVG (Scalable Vector Graphics) is an XML format used for drawing. You can think of SVG in a lot of the same terms as the DOM – there are elements with parents and children and attributes, and you can respond to the same mouse/touch events.

Even CSS styles and selectors can apply to SVG elements. The CSS attribute names for SVG come from the SVG definition, so they are sometimes different from their HTML brethren. (For example, to change the background color of a div to red you would select it then set background-color: red but to get the same effect on an SVG rectangle you would instead use the attribute fill: red since an SVG rect doesn’t respond to background-color for styling.)

SVG defines tags for lots of basic shapes, like <rect> and <circle> and <line>.

<svg width="300" height="180">
  <circle cx="30"  cy="50" r="25" />
  <circle cx="90"  cy="50" r="25" class="red" />
  <circle cx="150" cy="50" r="25" class="fancy" />

  <rect x="10"  y="80" width="40" height="40"
    fill="steelBlue" />
  <rect x="70"  y="80" width="40" height="40"
    style="fill: steelBlue" />
  <rect x="130" y="80" width="40" height="40"
    class="fancy" />
</svg>
    
.red {
  fill: red; /* not background-color! */
}

.fancy {
  fill: none;
  stroke: black; /* similar to border-color */
  stroke-width: 3pt; /* similar to border-width */
  stroke-dasharray: 3,5,10;
}


    

Where HTML has the <div> and <span> tags, SVG has the <g> tag for an arbitrary group. You’ll see <g> a lot in D3 examples. They’re great for applying styles to a group (including re-positioning the groups).

The <text> tag is good for simple labels. The <path> tag is powerful but complex, it can be used for either lines or arbitrary filled-in shapes depending on the styling.

<svg width="300" height="180">
  <g transform="translate(5, 15)">
    <text x="0" y="0">Howdy!</text>
  </g>

  <g transform="translate(5, 55)">
    <!-- M: move to (jump)
         L: line to
         Q: curve to (quadratic) -->
    <path d="M0,50 L50,0 Q100,0 100,50"
      fill="none" stroke-width="3" stroke="black" />
  </g>

  <g transform="translate(5, 105)">
    <!-- C: curve to (cubic)
         Z: close shape -->
    <path d="M0,100 C0,0 25,0 125,100 Z" fill="black" />
  </g>
</svg>
    
Howdy!

Parts of a Graph

Fork me on GitHub

Example

Let’s say you have a table in Excel and want to graph it. It’s easy! Just point it at your rows and columns, set a few colors, and boom! A graph.

Date Amount
2014-01-01 $10
2014-02-01 $20
2014-03-01 $40
2014-04-01 $80

So now we want to make one of these in SVG to show it off on the interwebs. It’s going to be a bit more work. What do we need to make sure we get it right?

The Scale

This graph has to be “to scale”. It has to have a coordinate system!

The x-axis goes from January 2014 to April 2014, and the y-axis goes from $0 to $80. However, the SVG is drawn in a box that’s about 200 by 300 pixels. Dates and pixels don’t map to one another on their own, so we have to specify a mapping somehow.

Note that the y-axis flips! The SVG origin, (0, 0) is in the top left, but in this graph, the origin is the bottom left. We call the chart y-up and we call SVG y-down.

The Axes

We can actually read the Excel graph because it’s clearly labeled. Those same labels with “$20” and “February” have to get to our screen somehow. They also need to be formatted correctly for the data type.

The Data

Our graph is showing our data! Somehow, the 4 rows in our source table need to turn into 4 points on a line. On top of that, the points in the line need to fit into the coordinate system we’ve defined.

We can kind of intuit this, but it’s critical to working with D3. We have data coming in, and we transform it to something visual.

Doing It The Hard Way

Let’s make a graph the hard way! As we’ve seen earlier, the SVG <path> tag is kind of complex, so we’ll swap out a line graph for a scatterplot.

We’ll need to manually write out each point. Transform attributes are inherited by child elements, so we can use <g> tags to move entire groups, such as the axes, or even offset the entire graph by a margin.

<svg width="350" height="160">
  <!-- 60px x 10px margin -->
  <g class="layer" transform="translate(60,10)">
    <!-- cx = 270px * ($X / 3)
                ^      ^   ^
    width of graph  x-value max(x)

          cy = 120px - (($Y / 80) * 120px)
                 ^       ^     ^       ^
      top of graph   y-value  max(y)  scale -->
    <circle r="5" cx="0"   cy="105" />
    <circle r="5" cx="90"  cy="90"  />
    <circle r="5" cx="180" cy="60"  />
    <circle r="5" cx="270" cy="0"   />

    <g class="y axis">
      <line x1="0" y1="0" x2="0" y2="120" />
      <text x="-40" y="105" dy="5">$10</text>
      <text x="-40" y="0"   dy="5">$80</text>
    </g>
    <g class="x axis" transform="translate(0, 120)">
      <line x1="0" y1="0" x2="270" y2="0" />
      <text x="-30"   y="20">January 2014</text>
      <text x="240" y="20">April</text>
    </g>
  </g>
</svg>
    
$10 $80 January 2014 April

Man! All that work for such a simple graph? SVG is a lot of work!

Doing It The D3 Way

Good news! D3 has pieces to help with each of the parts of a graph we listed above! However, D3 does this in the spirit of “automating the hard bits you already understand”, rather than making it all happen.

Small Helpers

There are a few operations that come up all the time, such as finding the minimum and maximum values of a data set (even both at the same time, the “extent”).

In D3, our source data is always plain old Javascript objects (POJOs). Most often the data is homogenous arrays.

var numbers = [ 5, 4, 10, 1 ],
    data = [
      { date: '2014-01-01', amount: 10 },
      { date: '2014-02-01', amount: 20 },
      { date: '2014-03-01', amount: 40 },
      { date: '2014-04-01', amount: 80 }
    ];

d3.min(numbers);
// 1
    

In D3 code, it’s common to pass callbacks that are used on all elements of a group. These callbacks are almost always called back with two arguments: the element and its index. It’s common to name these parameters d and i respectively.

d3.max(data, function(d, i) { return d.amount });
// 80

d3.extent(numbers);
// [1, 10]
    

Scales

D3 has objects called scales that help map values across coordinate systems. There are different kinds of scales (linear, logarithmic, linear for time). Scales are configured with a domain and a range, they map from the data to the approprate part of the screen (screen space).

Here is how we set up the y-scale for the above money example:

var y = d3.scaleLinear()
  .domain([0, 80]) // $0 to $80
  .range([200, 0]); // Seems backwards because SVG is y-down
    

Or if we wanted to take advantage of the helper methods above:

y.domain(d3.extent(data, function(d) { return d.amount }));
    

The domain is in the data space, so its units are your source units. The range is in screen space (pixels).

This scale object is also a function! Calling the scale as a function is how we translate values from one coordinate to another.

y(0);   // in: $0
// 200  // out: 200px (bottom of graph)
y(80);  // in: $80
// 0    // out: 0px (top of graph)
    

We can even do the same things with dates!

var x = d3.scaleTime()
    .domain([
      new Date(Date.parse('2014-01-01')),
      new Date(Date.parse('2014-04-01'))
    ])
    .range([0, 300]);

x(new Date(Date.parse('2014-02-01')));
// 103.3811949976841
    

Scales are not just for linear transforms (continuous or quantitative scales), they can also be used for arbitrary transforms (discrete or ordinal scales). We’ll come across more scales later.

Axes

In our example, up top, we have these nice labels and tick marks. This is something D3 can do for us. We can build an axis, and apply it to a scale. We say, “hey, I want to build an axis that”.

// x is the d3.scaleTime()
var xAxis = d3.axisBottom(x)
  .ticks(4); // specify the number of ticks

var svg = d3.select('body')
  .append('svg')        // create an <svg> element
    .attr('width', 300) // set its dimentions
    .attr('height', 150);

svg.append('g')            // create a <g> element
  .attr('class', 'x axis') // specify classes
  .call(xAxis);            // let the axis do its thing


    

D3’s axes are really powerful! Notice how we built it using Date objects, and by default, it labeled the tick marks appopriately!

Data

The next thing to do is take our data and transform it into something visible. This is data binding, and it’s a big topic, so it gets its own section.

Data Binding

Fork me on GitHub
Heads up! Data binding is probably the hardest part of D3 to "get". Personally, it took this being re-explained like 2 or 3 times to really internalize what was going on.

D3 selections are a different way to look at data binding. They’re powerful because the same selection can be updated for different data later on. Updating is the most powerful part of selections.

Selections d3.selectAll

Ok, so we’ve referenced d3.select() and d3.selectAll() a few times already but now, it’s really time to dig in. d3.select() will find one element, d3.selectAll will match all available elements.

With types, the functions might look something like:

d3.select(String selector) -> (d3.selection)

D3 selections are a group of elements that match a query or could match a query later (the elements may not have been constructed yet).

Joins selection.data()

Selections are used to map pieces of our data to elements in the DOM. Suppose we have some data:

var sales = [
  { product: 'Hoodie',  count: 7 },
  { product: 'Jacket',  count: 6 },
  { product: 'Snuggie', count: 9 },
];
    

And we want to map these to points on a scatterplot. We know we want each object in this array to turn into a <rect> tag, inside of our <svg> below:

<!-- before, empty graph -->
<svg>
</svg>
    
<!-- after, rects graph -->
<svg>
  <rect /><!-- { product: 'Hoodie',  count: 7 } -->
  <rect /><!-- { product: 'Jacket',  count: 6 } -->
  <rect /><!-- { product: 'Snuggie', count: 9 } -->
</svg>
    

To connect these, we’re going to create a selection and use .data() to bind our data to the selection.

var svg = d3.select('svg');
svg.size();
// 1 -- one <svg> element exists

var rects = svg.selectAll('rect')
  .data(sales);

rects.size();
// 0 -- no <rect> elements exist yet!
    

Okay, now we have a selection but still no elements! We have more work to do.

Adding Elements selection.enter()

Again, our goal is to have a rectangle for each data point. We are starting with none and we have 4 new data points, so obviously the right thing to do is to add a new <rect> for each data point.

The way D3 looks at this is a more subtle: we want to add a <rect> per data point, but only for the new points since the last data join. Since this is the first data binding (there are no rects currently), everything is new, it’s straightforward to add new points. It’s important to keep in mind that for the next selection, things will be more complex since there will already be rects.

The part of a D3 selection that represents these element-less data-points is selection.enter();

var newRects = rects.enter();
    

So now newRects represents these element-less data-points, so we use append to add new elements. The elements don’t add themselves, we have to create the elements that will match the selection ourselves. We use the same attribute editing helpers to configure each circle per its data point.

// recall that scales are functions that map from
// data space to screen space
var maxCount = d3.max(sales, function(d, i) {
  return d.count;
});
var x = d3.scaleLinear()
  .range([0, 300])
  .domain([0, maxCount]);
var y = d3.scaleOrdinal()
  .rangeRoundBands([0, 75])
  .domain(sales.map(function(d, i) {
    return d.product;
  }));

newRects.append('rect')
  .attr('x', x(0))
  .attr('y', function(d, i) {
    return y(d.product);
  })
  .attr('height', y.rangeBand())
  .attr('width', function(d, i) {
    return x(d.count);
  });
    
We're getting a little sneaky here! We're introducing an ordinal scale, one that's discrete instead of continuous.

The d3.scaleOrdinal() helps us create buckets for each element. In this case, that's one per product.

The domain is the 3 product names. The range is a little different, rangeRoundBands is a helper function that sets the range, but tells D3 to pick buckets that are whole pixel widths (no fractions).

So how does it turn out? Let’s take a look:

<svg width="300" height="100">
  <rect x="0" y="0"  height="25" width="233.33" />
  <rect x="0" y="25" height="25" width="200" />
  <rect x="0" y="50" height="25" width="300" />
<svg>
    
Check out how these attribute helpers can take immediate values as well as callbacks. Just like with d3.min, these callbacks use the same style of (d, i) parameters to represent the element and its index.

Removing Elements selection.exit()

Where selection.enter() selects elements that have added since the last data join, selection.exit() is the opposite, it applies to elements that have been removed.

Suppose we drop the first point from our source array, we can find and operate on the corresponding element in the DOM via selection.exit().

We can use the remove() method to immediately delete matched elements, it’s the opposite of append().

sales.pop(); // drops the last element

var rects = rects.data(sales); // join the data again

var rectsToRemove = rects.exit();

rectsToRemove.size()
// 1 -- one element is part of the exit selection

rectsToRemove.remove(); // instantly removes
    

Identity and the Key Function

As a quick aside: Javascript object equality is very shallow. Objects are only equal if they are actually the same object (identity), not if they have the same values:

var obj1 = { value: 1 };
// true -- identity
obj1 == obj1;

var obj2 = { value: 2 };
var obj3 = { value: 2 };
// false -- huh? they have the same values!
obj2 == obj3;
    

But the example with selection.exit() above works! It only removed one element from the DOM because we only removed one element from the array, and all the rest of the objects were the exact same.

What if we get a new page of data, with some overlap, but we no longer have the exact same object instances? Well, we will have to find some way to match objects to each other, and with D3, that’s where a key function comes in.

When we introduced selection.data() earlier, we left out the hidden second parameter, the key function. It’s another (d, i) callback.

This example keys objects on their date, so we can match elements across separate arrays.

var sales1 = [
  { product: 'Hoodie', count: 7 },
  { product: 'Jacket', count: 6 }
];

var sales2 = [
  { product: 'Jacket',  count: 6 }, // same
  { product: 'Snuggie', count: 9 }  // new
];

var rects = svg.selectAll('rect')
  .data(sales1, function(d, i) { return d.product; } );

rects.enter().append('rect');

rects.size();
// 2 -- first join, adds two new elements

var nextrects = rects
  .data(sales2, function(d, i) { return d.product; });

nextrects.exit().size();
// 1 -- one element to remove
nextrects.exit().remove();

nextrects.enter().append('rect'); // adds one element
    

Transitions selection.transition()

The key function is also important in case parts of our objects change – if we change a count, then we can update the appropriate element without having to delete and re-add the element, we can update it in place.

One of D3’s most visually pleasing features is its ability to help with transitions. The key function is critical here for object permanence.

Suppose we have per-product sales we want to update as more products are sold? We can use transitions to demonstrate this update.

Day 1
Product Sales (Cumulative)
Hoodie 10
Jacket 3
Snuggie 2
Day 2
Product Sales (Cumulative)
Hoodie 16
Jacket 7
Snuggie 8
function toggle() {
  sales = (sales == days[0]) ? days[1] : days[0];
  update();
}

function update() {
  var rects = svg.selectAll('rect')
    .data(sales, function(d, i) { return d.product });

  // When we enter, we add the DOM element
  // and set up the things that won't change
  var enterRects = rects.enter()
    .append('rect')
      .attr('x', x(0))
      .attr('y', function(d, i) {
        return y(d.product);
      })
      .attr('height', y.bandwidth())

  // "rects" represents the update selection, we need to
  // manually merge it with the enter selection to update
  // all rects at the same time
  rects.merge(enterRects)
    .attr('width', function(d, i) {
      return x(d.count);
    });
};
    

Ok, but now time to make it pretty. That’s where selection.transition() comes in. In the above example, we were just using the plain update selection to change the values. Here, we’ll use transition() to make our transition much slicker.

transition() selections can have custom timing attributes like .duration() and .delay() and even a custom easing function .ease(), but the defaults are pretty nice.

function toggle() {
  sales = (sales == days[0]) ? days[1] : days[0];
  update();
}

function update() {
  var rects = svg.selectAll('rect')
    .data(sales, function(d, i) { return d.product });

  var enterRects = rects.enter()
    .append('rect')
      .attr('x', x(0))
      .attr('y', function(d, i) {
        return y(d.product);
      })
      .attr('height', y.bandwidth())
      .attr('width', function(d, i) {
        return x(d.count);
      });

  rects.merge(enterRects)
    .transition() // NEW
    .duration(1000) // Also NEW
      .attr('width', function(d, i) {
        return x(d.count);
      });
};

    

Ok! That was the basics of D3! We’ve got a few more complex examples, but they mostly build on what we’ve already shown.

Examples

Fork me on GitHub

There’s a lot more to D3! This is just a quick tour of some other stuff D3 has to offer.

Layouts and SVG Helpers

Some of these examples make use of D3’s layout helpers.

Some layouts convert our original data into descriptions of the shapes we want to draw. For example, the pie layout converts numbers into arcs (start and end angles for pie slices). This keeps our drawing code simple. Other layouts help us group our data so we can draw useful shapes like stacked stacked or trees.

D3 also provides helpers to make some of the more complex SVG shapes easier to draw. The path helper can build curves that interolate between data points. The arc helper can take the angles generated by the pie layout and draw arcs (pie slices).

A Pie Chart

Let’s start out by walking through using D3 to draw a simple pie chart.

First we get our source data:

var sales = [
  { product: 'Hoodie',  count: 12 },
  { product: 'Jacket',  count: 7 },
  { product: 'Snuggie', count: 6 },
];
    

We want each product to be represented as a pie slice in our pie chart, which involves calculating the associated angles. We’ll use the d3.pie helper for that:

var pie = d3.pie()
  .value(function(d) { return d.count })

var slices = pie(sales);
// the result looks roughly like this:
[
  {
    data: sales[0],
    endAngle: 3.0159289474462017,
    startAngle: 0,
    value: 12
  },
  {
    data: sales[1],
    startAngle: 3.0159289474462017,
    endAngle: 4.775220833456486,
    value: 7
  },
  {
    data: sales[2],
    startAngle: 4.775220833456486,
    endAngle: 6.283185307179587,
    value: 6
  }
]
    

Now we have our data in angles (radians), so we can turn them into something visual. The next tool D3 gives us is the d3.arc which helps to create SVG <path> tags for arcs. This is where we provide all the information relevant to actually drawing, such as the radius size.

var arc = d3.arc()
  .innerRadius(0)
  .outerRadius(50);

// helper that returns a color based on an ID
var color = d3.scaleOrdinal(d3.schemeCategory10);

var svg = d3.select('svg.pie');
var g = svg.append('g')
  .attr('transform', 'translate(200, 50)')

g.selectAll('path.slice')
  .data(slices)
    .enter()
      .append('path')
        .attr('class', 'slice')
        .attr('d', arc)
        .attr('fill', function(d) {
          return color(d.data.product);
        });

// building a legend is as simple as binding
// more elements to the same data. in this case,
// <text> tags
svg.append('g')
  .attr('class', 'legend')
    .selectAll('text')
    .data(slices)
      .enter()
        .append('text')
          .text(function(d) { return '• ' + d.data.product; })
          .attr('fill', function(d) { return color(d.data.product); })
          .attr('y', function(d, i) { return 20 * (i + 1); })
    
Again, we snuck in a new helper, and it's another type of ordinal scale.
The d3.scaleOrdinal helper gives us back a function. This function takes in values (typically IDs) and gives back a value in its domain. The same ID gets the same color, and it will rotate through its domain. apart. We initalize it with d3.schemeCategory10 which is a list of10 colors that are pretty easy to tell apart.

Stacked Bars

One of the most common charts to draw is some variation of stacked bars. These are deceptively complex, because after the first layer, each new layer of bars depends on layout of the previous one.

The data requirements are also different because stacked bars need to have dense data sources. In most graphs, we could omit an empty value because it won’t be drawn, but in a stacked layout, that still could affect the layout of the next layer.

Let’s start we have sales of our products over multiple days.

Sales
Date Hoodie Jacket Snuggie
2014-01-01 6 2 3
2014-01-02 7 5 2
2014-01-03 8 7 3

Transformed into a dense array, our data looks like this:

var sales = [
  { date: "2014-01-01", hoodies: 6, jackets: 2, snuggies: 3 },
  { date: "2014-01-02", hoodies: 7, jackets: 5, snuggies: 2 },
  { date: "2014-01-03", hoodies: 8, jackets: 7, snuggies: 3 }
];
    

Now we can take advantage of the d3.stack to do the work of stacking our layers on top each other. While normally a bar graph would have one y value, a stacked one has two:

  • where a segment starts (“baseline”)
  • where the segment ends

For the first layer stacked bar chart (at the bottom), the baseline is typically 0. It can be other values for things like streamgraphs, which are a whole other topic.

var stack = d3.stack()
  .keys(["hoodies", "jackets", "snuggies"])

var stacked = stack(sales);
    

Now, stacked will be a set of nested arrays containing the hights of the data in sales, stacked, which will come in handy when it’s time to draw these. For examples, the stacked data now looks like this:

stacked
[
  [[0, 6],  [0, 7],   [0, 8  ]],
  [[6, 8],  [7, 12],  [8, 15 ]],
  [[8, 11], [12, 14], [15 18 ]]
]
   

But the data is not a plain array! It also has a few useful properties. The “rows” have key and index and the computed start/end arrays also have data – the original data.

stacked
// [Array[3], Array[3], Array[3]]
stacked[0]
// [Array[2], Array[2], Array[2]]
Object.keys(stacked[0])
// ["0", "1", "2", "key", "index"]
stacked[0].key
// "hoodies"
stacked[0][0].data
// {date: "2014-01-01", hoodies: 6, jackets: 2, snuggies: 3}
    

Ok so let’s get to drawing! We’ll bring back our good friends d3.scaleLinear and d3.scaleTime.

var height = 200;
var width = 200;

// we need to calculate the maximum y-value
// across all our layers, so we find the biggest
// end value
var maxY = d3.max(stacked, function(d) {
  return d3.max(d, function(d) {
    return d[1];
  });
});

var y = d3.scaleLinear()
  .range([height, 0])
  .domain([0, maxY]);

var x = d3.scaleTime()
  .range([0, width])
  .domain(d3.extent(sales, function(d) {
    return new Date(Date.parse(d.date));
  }))
  .nice(4);

var svg = d3.select('svg.stack');
var color = d3.scaleOrdinal(d3.schemeCategory10);

// bind a <g> tag for each layer
var layers = svg.selectAll('g.layer')
  .data(stacked, function(d) { return d.key; })
    .enter()
      .append('g')
        .attr('class', 'layer')
        .attr('fill', function(d) { return color(d.key); })

// bind a <rect> to each value inside the layer
layers.selectAll('rect')
  .data(function(d) { return d; })
  .enter()
    .append('rect')
      .attr('x', function(d) { return x(new Date(Date.parse(d.data.date))); })
      .attr('width', width / 3)
      .attr('y', function(d) {
        // remember that SVG is y-down while our graph is y-up!
        // here, we set the top-left of this bar segment to the
        // larger value of the pair
        return y(d[1]);
      }).attr('height', function(d) {
        // since we are drawing our bar from the top downwards,
        // the length of the bar is the distance between our points
        return y(d[0]) - y(d[1]);
      });
    

There are a few things that make this graph a little more complex. One of the hardest parts is realizing that D3 is really only going to hint at how we should stack the bars: D3 gives us stacked results in our data space, but not in SVG's coordinate system. We have to deal with the same confusing Y-axis coordinate flip.

Onward!

D3 has a lot to offer, and our goal here was to give a brief tour and cover some core concepts! There’s much more to learn about D3, but hopefully this tutorial has given you enough so that you can teach yourself the rest.

There are lot of great resources for learning D3 out there:

  1. First and foremost, D3’s own wiki. This is a great starting point for any D3-related exploration

  2. Nestled inside that wiki, the D3 API Reference is great for remembering what APIs there are and what the various parameters mean.

  3. For more examples of what is possible with D3 check out the D3 examples by creator of D3, Mike Bostock.

But don’t stop there! Google searches are a great way to discover things too. Happy visualizing!